package com.anupam;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import com.anupam.Grid.Cell;
import com.anupam.PatternReader.Pattern;

/**
 * This is the main entry point of the application.
 *
 *  It reads the data.json file for inputs and applies the rules governing the state of a cell in a
 * particular grid.
 *
 * The class encapsulates no state except for the file path.
 *
 * @author anupamsaini@gmail.com (Anupam Saini)
 *
 */
public class GameOfLife {


  /**
   * The file containing JSON formatted input.
   */
  public static final String inputFile = "data.json";

  /**
   * Read the data file and populate grid as per input.
   *
   * @return A List of grids.
   * @throws IOException
   */
  public List<Grid> populateGrids() throws IOException {
    List<Pattern> patterns = (new PatternReader()).readJsonStream(
        GameOfLife.class.getResourceAsStream(inputFile));
    List<Grid> grids = new ArrayList<Grid>();
    for (Pattern patt : patterns) {
      List<Integer> dimension = patt.getDimension();
      int xAxis = dimension.get(0);
      int yAxis = dimension.get(1);
      Grid grid = new Grid(xAxis, yAxis);
      grid.generateGrid(patt.getState());
      grids.add(grid);
    }
    return grids;
  }

  /**
   * Entry point for iterating over all cells in the grid and updating the live neighbor count of
   * all the living cells in the matrix.
   *
   * @param grid The Grid object reprsentation of a input matrix.
   */
  private void scan(Grid grid) {
    Cell[][] cells = grid.getGrid();
    for (int i = 0; i < grid.getxAxisSize(); i++) {
      for (int j = 0; j < grid.getyAxisSize(); j++) {
        if (cells[i][j].isAlive()) {
          this.update(grid, i, j);
        }
      }
    }
  }

  /**
   * For a alive cell updates the {@link Cell.liveNeighbours} count of all the Cells in it's
   * vicinity.
   *
   * @param grid
   * @param i
   * @param j
   */
  private void update(Grid grid, int i, int j) {
    ArrayList<int[]> nbrs = getValidNeighbours(grid, i, j);
    Cell[][] cells = grid.getGrid();
    for (int[] nbr : nbrs) {
      cells[nbr[0]][nbr[1]].incLiveNeighbours();
    }
  }

  /**
   * Fetches all valid neighbours of the Cell whose indexs have been passed.
   *
   * @param grid Container of a M*N matrix and has state info of each cell in it.
   *
   * @param i Index in the 2D array {@link Grid grid} of cells.
   * @param j Index in the 2D array {@link Grid grid} of cells.
   * @return
   */
  private ArrayList<int[]> getValidNeighbours(Grid grid, int i, int j) {
    ArrayList<int[]> nbrs = new ArrayList<int[]>();
    // 8 possible neighbor directions.
    int[][] dirs = new int[][] { {i - 1, j - 1},
        {i - 1, j},
        {i - 1, j + 1},
        {i, j - 1},
        {i, j + 1},
        {i + 1, j - 1},
        {i + 1, j},
        {i + 1, j + 1}};
    // dropping border cases
    for (int k = 0; k < dirs.length; k++) {
      if (!((dirs[k][0] < 0) || (dirs[k][0] >= grid.getxAxisSize()) || (dirs[k][1] < 0)
          || (dirs[k][1] >= grid.getyAxisSize()))) {
        nbrs.add(dirs[k]);
      }
    }
    return nbrs;
  }

  /**
   * Apply the rules governing a state of a cell to all the cells in the grid.
   *
   * @param grid
   */
  public void tick(Grid grid) {
    Cell[][] cells = grid.getGrid();
    for (int i = 0; i < grid.getxAxisSize(); i++) {
      for (int j = 0; j < grid.getyAxisSize(); j++) {
        Cell cell = cells[i][j];
        int liveNbrs = cell.getLiveNeighbours();
        // Rules for changing state of a Live cell.
        if (cell.isAlive()) {
          if (liveNbrs < 2 || liveNbrs > 3) {
            // Kill the cell as it violates the rules.
            cell.setState(false);
          }
        } else {
          // Rule for changing state of a dead cell.
          if (liveNbrs == 3) {
            // Revive the dead cell.
            cell.setState(true);
          }
        }
      }
    }
  }

  public static void main(String args[]) throws IOException {
    GameOfLife gof = new GameOfLife();
    List<Grid> grids = gof.populateGrids();
    for (Grid grid : grids) {
      System.out.println("Before flipping");
      System.out.println(grid.toString());
      gof.scan(grid);
      gof.tick(grid);
      System.out.println("After flipping");
      System.out.println(grid.toString());
    }
  }
}
